package security

import (
	"context"
	"database/sql"
	"sync"

	"gitee.com/hp-huiw/family-expense-api/internal/repository"
	"gitee.com/hp-huiw/my-go-core/util"
	"github.com/samber/lo"
)

var cachedUserActiveTokens []repository.UserActiveToken
var cachedUserActiveTokensLock sync.RWMutex

// type UserCachedActiveTokenManager interface {
// 	GetCachedUserActiveTokens(ctx context.Context) ([]repository.UserActiveToken, error)
// 	ReloadCachedUserActiveTokens(ctx context.Context) error
// }

type UserActiveTokenManagerRepository struct {
	db                        *sql.DB
	userActiveTokenRepository repository.UserActiveTokenRepository
}

func NewUserActiveTokenManagerRepository(db *sql.DB) *UserActiveTokenManagerRepository {
	return &UserActiveTokenManagerRepository{
		db:                        db,
		userActiveTokenRepository: repository.NewUserActiveTokenRepository(db),
	}
}

func (r *UserActiveTokenManagerRepository) GetCachedUserActiveTokens(ctx context.Context) ([]repository.UserActiveToken, error) {
	cachedUserActiveTokensLock.Lock()
	isEmpty := len(cachedUserActiveTokens) == 0

	// IMPORTANT: RUnlock immediately because ReloadCachedRequestMaps() uses write lock inside.
	// This is to avoid deadlock.
	cachedUserActiveTokensLock.Unlock()

	if isEmpty {
		if err := r.ReloadCachedUserActiveTokens(ctx); err != nil {
			return nil, err
		}
	}

	cachedUserActiveTokensLock.RLock()
	defer cachedUserActiveTokensLock.RUnlock()

	return cachedUserActiveTokens, nil
}

func (r *UserActiveTokenManagerRepository) ReloadCachedUserActiveTokens(ctx context.Context) error {
	cachedUserActiveTokensLock.Lock()
	defer cachedUserActiveTokensLock.Unlock()

	result, err := r.userActiveTokenRepository.Query(ctx)

	if err != nil {
		return err
	}

	cachedUserActiveTokens = result

	return nil
}

func (r *UserActiveTokenManagerRepository) IsActiveAccessToken(ctx context.Context, username string, deviceID *string, accessToken string) bool {
	cachedUserActiveTokens, err := r.GetCachedUserActiveTokens(ctx)

	if err != nil {
		return false
	}

	found := lo.ContainsBy(cachedUserActiveTokens, func(item repository.UserActiveToken) bool {
		return item.Username == username &&
			item.AccessToken == accessToken &&
			util.PtrEqual(item.DeviceID, deviceID)
	})

	return found
}
